﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

using System.Runtime.Remoting.Proxies;
using MessageCallAspect.Inspector.Attributes;
using MessageCallAspect.Inspector.MessageSink;
using System.Runtime.Remoting;

namespace MessageCallAspect.Inspector
{
    public class TargetInspectorProxy<TTarget> : RealProxy, IDisposable
      where TTarget : class
    {
        #region fields

        private readonly TTarget m_Target;
        private readonly IModuleContext m_Context;
        private readonly object m_CacheLocker = new object();
        private readonly Dictionary<RuntimeMethodHandle, IMessageSink> m_Cache = new Dictionary<RuntimeMethodHandle, IMessageSink>();
        private readonly Dictionary<RuntimeMethodHandle, List<IMessageSinkCreator>> m_MessageSinkCreatorDic = new Dictionary<RuntimeMethodHandle, List<IMessageSinkCreator>>();

        #endregion

        #region ctor

        public TargetInspectorProxy(TTarget target, IModuleContext context, params IMessageSinkCreator[] dynamicMessageSinkCreator)
            : base(typeof(TTarget))
        {
            m_Target = target;
            m_Context = context;

            IMessageSinkCreator[] classMessageSinkCreators = target.GetType().GetAllAttributes<MessageSinkAttribute>().Cast<IMessageSinkCreator>().ToArray();
            IEnumerable<MethodInfo> abstractTargetMethods = GetMethods(typeof(TTarget), true);
            IEnumerable<MethodInfo> concreteTargetMethods = GetMethods(target.GetType());

            foreach (MethodInfo method in abstractTargetMethods)
            {
                MethodInfo concreteTargetMethod = (from m in concreteTargetMethods
                                                   where m.ToString() == method.ToString()
                                                   select m).FirstOrDefault();

                if (concreteTargetMethod == null || concreteTargetMethod.HasAttribute<InspectorIgnoreAttribute>())
                    continue;

                IMessageSinkCreator[] methodMessageSinkCreators = concreteTargetMethod.GetAllAttributes<MessageSinkAttribute>().Cast<IMessageSinkCreator>().ToArray();
                List<IMessageSinkCreator> messageSinkCreators = methodMessageSinkCreators.Union(classMessageSinkCreators)
                                                                .Union(dynamicMessageSinkCreator).Distinct().ToList();
                
                RuntimeMethodHandle key = GetMethodHandle(method);
                if (!m_MessageSinkCreatorDic.ContainsKey(key))
                {
                    m_MessageSinkCreatorDic.Add(key, new List<IMessageSinkCreator>());

                }
                m_MessageSinkCreatorDic[key].AddRange(messageSinkCreators.OrderBy(att => att.Order));
            }
        }

        #endregion

        public TTarget TransparentProxy
        {
            get
            {
                return base.GetTransparentProxy() as TTarget;
            }
        }

        public override IMessage Invoke(IMessage msg)
        {
            try
            {
                IMessageSink sink = GetMessageSinkChain(msg);
                return sink.SyncProcessMessage(msg);
            }
            catch (Exception ex)
            {
                return ex.Wrap(msg);
            }

        }
       
        /// <summary>
        /// create message interception sink chain
        /// </summary>
        /// <param name="context"></param>
        /// <param name="target"></param>
        /// <param name="messageSinkCreators"></param>
        /// <returns></returns>
        protected virtual IMessageSink CreateMessageSinkChain(IModuleContext context, object target, IEnumerable<IMessageSinkCreator> messageSinkCreators)
        {
            IMessageSink nextSink = new EndpointSink(target);
            foreach (IMessageSinkCreator creator in messageSinkCreators.Reverse())
            {
                nextSink = creator.CreateMessageSink(context, m_Target, nextSink);
            }
            return nextSink;
        }

        private IMessageSink GetMessageSinkChain(IMessage msg)
        {
            IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)msg);
            RuntimeMethodHandle handle = GetMethodHandle((MethodInfo)methodMessage.MethodBase);

            if (!m_Cache.ContainsKey(handle))
            {
                lock (m_CacheLocker)
                {
                    if (!m_Cache.ContainsKey(handle))
                    {
                        List<IMessageSinkCreator> matches = new List<IMessageSinkCreator>();
                        if (m_MessageSinkCreatorDic.ContainsKey(handle))
                            matches.AddRange(m_MessageSinkCreatorDic[handle]);

                        IMessageSink messageSinkChain = CreateMessageSinkChain(m_Context, m_Target, matches);
                        m_Cache.Add(handle, messageSinkChain);
                    }
                }
            }
            return m_Cache[handle];
        }

        /// <summary>
        /// get method uid
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        private static RuntimeMethodHandle GetMethodHandle(MethodInfo method)
        {
            RuntimeMethodHandle handle;

            if (method.IsGenericMethod)
                handle = method.GetGenericMethodDefinition().MethodHandle;
            else
                handle = method.MethodHandle;

            return handle;
        }

        private static IEnumerable<MethodInfo> GetMethods(Type type)
        {
            return type.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(m => m.DeclaringType != typeof(object));
        }

        private static IEnumerable<MethodInfo> GetMethods(Type type, bool includeInherited)
        {
            List<MethodInfo> methods = new List<MethodInfo>(GetMethods(type));

            if (includeInherited)
            {
                foreach (Type _type in type.GetInterfaces())
                {
                    methods.AddRange(GetMethods(_type).Except(methods, new MethodComparer()));

                }
            }
            return methods;
        }

        #region MethodComparer

        private class MethodComparer : IEqualityComparer<MethodInfo>
        {
            #region IEqualityComparer<MethodInfo> Members

            public bool Equals(MethodInfo x, MethodInfo y)
            {
                if (x.ToString().ToLower() == y.ToString().ToLower())
                    return true;
                else
                    return false;
            }

            public int GetHashCode(MethodInfo obj)
            {
                return obj.ToString().ToLower().GetHashCode();
            }

            #endregion
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion
    }

}
